#![feature(async_closure)]

mod app;

use actix_cors::Cors;
use actix_web::middleware::TrailingSlash;
use actix_web::{middleware, web, App, HttpServer};
use clap::Parser;
use std::collections::HashMap;
use std::fs;
use std::sync::Arc;
use tokio::sync::Mutex;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
    filename: String,
    #[arg(short, long, default_value = "127.0.0.1")]
    listen: String,
    #[arg(short, long, default_value_t = 8080)]
    port: u16,
}

use crate::app::handler::{
    get_by_id, get_list, get_record, list_add_one, list_remove, update_list, update_record,
};
use crate::app::state::{build_db, AppState, Dbs, DBS};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
    let args = Args::parse();

    // Read and parse JSON file
    let file_content = fs::read_to_string(args.filename).expect("Failed to read file");
    let router_map: HashMap<String, serde_json::Value> =
        serde_json::from_str(&file_content).expect("Failed to parse JSON");
    let (arr_db, obj_db) = build_db(&router_map);
    DBS.get_or_init(|| async {
        Dbs {
            arr_db: Arc::new(Mutex::new(arr_db)),
            obj_db: Arc::new(Mutex::new(obj_db)),
        }
    })
    .await;
    HttpServer::new(move || {
        let cors = Cors::default();
        let app = App::new()
            .wrap(cors)
            .wrap(middleware::NormalizePath::new(TrailingSlash::Trim))
            .wrap(middleware::Logger::default()) //正式环境可以注释此行 ***
            .app_data(web::Data::new(AppState::new(router_map.clone())));
        router_map.iter().fold(app, |acc, (k, v)| {
            let path = &format!("/{}", k);
            let path_with_id = &format!("/{}/{{id}}", k);
            if v.is_object() {
                acc.service(
                    web::resource(path)
                        .route(web::get().to(get_record))
                        .route(web::put().to(update_record))
                        .route(web::patch().to(update_record)),
                )
            } else if v.is_array() {
                acc.service(
                    web::resource(path)
                        .route(web::get().to(get_list))
                        .route(web::post().to(list_add_one)),
                )
                .service(
                    web::resource(path_with_id)
                        .route(web::get().to(get_by_id))
                        .route(web::put().to(update_list))
                        .route(web::patch().to(update_list))
                        .route(web::delete().to(list_remove)),
                )
            } else {
                acc
            }
        })
    })
    .bind((args.listen, args.port))?
    .run()
    .await
}
